package com.anttribe.components.db.executor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import com.anttribe.components.db.executor.config.SqlExecutorConfiguration;
import com.anttribe.components.db.executor.entity.SqlExecuteResult;
import com.anttribe.components.db.executor.exception.SqlExecutorExcetion;
import com.anttribe.components.db.executor.formatter.DataFormatter;
import com.anttribe.components.db.executor.formatter.DataFormatterRegistry;
import com.anttribe.components.db.metadata.utils.DbUtils;
import com.anttribe.components.infra.lang.Symbols;
import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;

/**
 * @author zhaoyong
 * @date 2023/1/4 0004
 */
@Slf4j
public class GeneralSqlExecutor implements SqlExecutor {

    private final DataSource dataSource;

    /**
     * 数据库连接
     */
    private Connection connection;

    /**
     * SqlExecutorConfiguration
     */
    private SqlExecutorConfiguration sqlExecutorConfiguration;

    public GeneralSqlExecutor(DataSource dataSource, SqlExecutorConfiguration sqlExecutorConfiguration) {
        this.dataSource = dataSource;
        this.sqlExecutorConfiguration = sqlExecutorConfiguration;
    }

    @Override
    public List<SqlExecuteResult> execute(String sql) {
        if (StringUtils.isEmpty(sql)) {
            throw new SqlExecutorExcetion("Provided empty sql for executing.");
        }

        List<SqlExecuteResult> sqlExecuteResults = Lists.newArrayList();
        if (StringUtils.indexOf(sql, Symbols.SEMICOLON) != -1) {
            String[] subExecuteSqls = StringUtils.split(sql, Symbols.SEMICOLON);
            sqlExecuteResults = executeInner(Lists.newArrayList(subExecuteSqls));
        }
        return sqlExecuteResults;
    }

    @Override
    public List<SqlExecuteResult> execute(List<String> sql) {
        if (CollectionUtils.isEmpty(sql)) {
            throw new SqlExecutorExcetion("Provided NON sql for executing.");
        }

        List<String> executeSqls = Lists.newArrayList();
        sql.forEach(executeSql -> {
            if (StringUtils.indexOf(executeSql, Symbols.SEMICOLON) != -1) {
                String[] subExecuteSqls = StringUtils.split(executeSql, Symbols.SEMICOLON);
                executeSqls.addAll(Lists.newArrayList(subExecuteSqls));
            } else {
                executeSqls.add(executeSql);
            }
        });

        return executeInner(executeSqls);
    }

    /**
     * 内部处理sql执行逻辑
     * 
     * @param sql
     *            sql语句
     * @return 执行结果
     */
    private List<SqlExecuteResult> executeInner(List<String> sql) {
        List<SqlExecuteResult> sqlExecuteResults = Lists.newArrayList();
        Connection conn = this.getConnection();
        try {
            sql.forEach(executeSql -> {
                try (PreparedStatement preparedStatement = conn.prepareStatement(executeSql)) {
                    setExecutorSettings(preparedStatement);
                    boolean result = preparedStatement.execute();
                    SqlExecuteResult sqlExecuteResult = processSqlExecuteResult(preparedStatement, result);
                    if (null != sqlExecuteResult) {
                        sqlExecuteResults.add(sqlExecuteResult);
                    }
                } catch (SQLException e) {
                    log.error("while executing sql:{} cause error:{}", executeSql, e);
                    sqlExecuteResults.add(SqlExecuteResult.fail("" + e.getErrorCode(), e.getLocalizedMessage()));
                }
            });
        } finally {
            try {
                DbUtils.close(conn);
            } catch (SQLException e) {
                throw new SqlExecutorExcetion(e);
            }
        }

        return sqlExecuteResults;
    }

    /**
     * 设置执行参数
     * 
     * @param preparedStatement
     *            PreparedStatement
     * @throws SQLException
     */
    private void setExecutorSettings(PreparedStatement preparedStatement) throws SQLException {
        preparedStatement.setMaxRows(sqlExecutorConfiguration.getSettings().getQueryMaxRows());
    }

    /**
     * 处理sql执行结果
     * 
     * @param preparedStatement
     *            PreparedStatement
     * @param result
     *            执行结果
     * @return SqlExecuteResult
     * @throws SQLException
     */
    private SqlExecuteResult processSqlExecuteResult(PreparedStatement preparedStatement, boolean result)
        throws SQLException {
        if (!result) {
            return SqlExecuteResult.success(preparedStatement.getUpdateCount());
        }

        ResultSet rs = preparedStatement.getResultSet();
        if (null == rs) {
            return SqlExecuteResult.success();
        }

        return processSqlExecuteResult(rs);
    }

    /**
     * 处理结果集
     * 
     * @param rs
     *            ResultSet
     * @return SqlExecuteResult
     */
    private SqlExecuteResult processSqlExecuteResult(ResultSet rs) throws SQLException {
        List<Map<String, Object>> resultDataList = new LinkedList<>();
        ResultSetMetaData rsMetaData = rs.getMetaData();
        while (rs.next()) {
            Map<String, Object> resultMap = new LinkedHashMap<>();
            for (int i = 0; i < rsMetaData.getColumnCount(); i++) {
                int columnIndex = i + 1;
                int columnType = rsMetaData.getColumnType(columnIndex);
                resultMap.put(rsMetaData.getColumnName(columnIndex), formatColumnValue(columnIndex, columnType, rs));
            }
            resultDataList.add(resultMap);
        }
        return SqlExecuteResult.success(resultDataList);
    }

    /**
     * 格式化列值
     * 
     * @param columnIndex
     *            列索引
     * @param columnType
     *            列数据类型
     * @param rs
     *            ResultSet
     * @return String
     */
    private String formatColumnValue(int columnIndex, int columnType, ResultSet rs) throws SQLException {
        DataFormatter dataFormatter = DataFormatterRegistry.me().getDataFormatter(columnType);
        Object columnValue = rs.getObject(columnIndex);
        if (null != dataFormatter && null != dataFormatter.getDataTypeClazz()) {
            columnValue = rs.getObject(columnIndex, dataFormatter.getDataTypeClazz());
        }
        return formatColumnValue(columnValue, dataFormatter);
    }

    /**
     * 格式化列值
     * 
     * @param columnValue
     *            列值
     * @param dataFormatter
     *            数据格式化器
     * @return String
     */
    private String formatColumnValue(Object columnValue, DataFormatter dataFormatter) {
        if (null == columnValue) {
            return DataFormatter.NULL_VALUE;
        }

        if (null == dataFormatter) {
            return String.valueOf(columnValue);
        }
        return dataFormatter.format(columnValue);
    }

    /**
     * 获取数据库连接
     *
     * @return Connection
     */
    private Connection getConnection() {
        try {
            if (null != this.connection && !this.connection.isClosed()) {
                return this.connection;
            }

            this.connection = this.dataSource.getConnection();
            return connection;
        } catch (SQLException e) {
            throw new SqlExecutorExcetion(e);
        }
    }

}
